﻿#pragma once

#include <hacks/base/hacks_conf.h>
#include <hacks/base/ev_loop.h>

namespace hacks {
    template<typename T>
    class future_core {
    DISABLE_COPY_MOVE(future_core)

    public:

        typedef std::function<void(T &)> listsner_type;

        bool is_ready() const {
            return static_cast<bool>(storage_);
        }

        void set_value(std::shared_ptr<T> &&v) {
            assert(storage_ == nullptr);
            hacks::spin_lock_guard g(fast_lock_);
            storage_ = v;
            cv_.notify_all();
            //notify listeners
            loop_->run_in_loop([listeners_ = listeners_, storage_ = storage_]() {
                std::for_each(listeners_->begin(),
                              listeners_->end(),
                              [&](listsner_type &v) {
                                  v(*storage_);
                              });
            });
        }

        void set_value(T &&v) {
            set_value(std::make_shared<T>(std::forward<T>(v)));
        }

        void set_value(const T &v) {
            set_value(std::make_shared<T>(std::forward<T>(v)));
        }

        T &get_value() {
            if (storage_) {
                return *storage_;
            }
            wait();
            return *storage_;
        }

        void wait() {
            std::unique_lock<std::mutex> lck(mtx_);
            cv_.wait(lck, [&]() {
                return is_ready();
            });
        }

        template<typename Rep, typename Period>
        void wait_for(std::chrono::duration<Rep, Period> ms) {
            std::unique_lock<std::mutex> lck(mtx_);
            cv_.wait_for(lck, ms, [&]() {
                return is_ready();
            });
        }

        void add_listener(listsner_type &&l) {
            hacks::spin_lock_guard g(fast_lock_);
            if (is_ready()) {
                loop_->run_in_loop([l = std::move(l), storage_ = storage_]() mutable {
                    l(*storage_);
                });
                return;
            }
            listeners_->emplace_back(l);
        }

        explicit future_core(const std::shared_ptr<ev_loop> &loop)
                : cv_(),
                  mtx_(),
                  storage_(nullptr),
                  listeners_(std::make_shared<std::list<listsner_type>>()),
                  loop_(loop),
                  fast_lock_() {
            assert(loop_);
        }

    private:
        std::condition_variable cv_;
        std::mutex mtx_;
        std::shared_ptr<T> storage_;
        std::shared_ptr<std::list<listsner_type>> listeners_;
        shared_ev_loop loop_;
        hacks::spin_lock fast_lock_;

    };

    template<typename T>
    using shared_future_core = std::shared_ptr<future_core<T>>;

    template<typename T>
    class future {

    public:
        explicit future(hacks::shared_future_core<T> core) : fcore_shared_(core) {
            assert(fcore_shared_);
        }

        void add_listener(typename hacks::future_core<T>::listsner_type l) {
            fcore_shared_->add_listener(l);
        }

        void syn() {
            fcore_shared_->wait();
        }

        template<typename Rep, typename Period>
        void syn_for(std::chrono::duration<Rep, Period> ms) {
            fcore_shared_->wait_for(ms);
        }

        bool is_ready() const {
            return fcore_shared_->is_ready();
        }

        future(const future &o)
                : fcore_shared_(o.fcore_shared_) {
        }

        future &operator=(const future &o) {
            this->copy(o);
            return *this;
        }

        future(future &&o) noexcept
                : fcore_shared_(std::move(o.fcore_shared_)) {
            o.fcore_shared_ = nullptr;
        }

        future &operator=(future &&o) noexcept {
            this->swap(std::forward<future>(o));
            return *this;
        }

    private:
        void copy(const future &o) {
            fcore_shared_ = o.fcore_shared_;
        }

        void swap(future &&o) noexcept {
            std::swap(fcore_shared_, o.fcore_shared_);
            o.fcore_shared_ = nullptr;
        }

    private:
        hacks::shared_future_core<T> fcore_shared_;
    };

    template<typename T>
    class promise {
    public:
        explicit promise(const shared_ev_loop &loop)
                : fcore_shared_(std::make_shared<future_core<T>>(loop)) {}

        void set_result(const T &v) {
            fcore_shared_->set_value(std::forward<T>(v));
        }

        void set_result(T &&v) {
            fcore_shared_->set_value(std::forward<T>(v));
        }

        bool is_ready() const {
            return fcore_shared_->is_ready();
        }

        future<T> get_future() {
            return future<T>(fcore_shared_);
        }

        promise(const promise &o)
                : fcore_shared_(o.fcore_shared_) {
        }

        promise &operator=(const promise &o) {
            this->copy(o);
            return *this;
        }

        promise(promise &&o) noexcept
                : fcore_shared_(std::move(o.fcore_shared_)) {
            o.fcore_shared_ = nullptr;
        }

        promise &operator=(promise &&o) noexcept {
            swap(std::forward<promise>(o));
            return *this;
        }

    private:
        void copy(const promise &o) {
            fcore_shared_ = o.fcore_shared_;
        }

        void swap(promise &&o) noexcept {
            std::swap(fcore_shared_, o.fcore_shared_);
            o.fcore_shared_ = nullptr;
        }

    private:
        hacks::shared_future_core<T> fcore_shared_;
    };
}
